﻿using System;
using System.Collections.Specialized;
using System.Collections;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Configuration;
using System.Threading;

using System.ComponentModel.Composition;
using EnergyService_CSharp;
using System.Xml;
using System.Windows.Forms;

namespace GooglePlugin
{
    [Export(typeof(IEnergyMonitorPlugin))]
    public class GooglePlugin : IEnergyMonitorPlugin
    {
        struct EnergyData
        {
            public DateTime startTime;
            public int duration;
            public float KWh;
        };

        // Plugin constants.
        const string pluginname = "GooglePlugin";
        const string plugindescription = "Post your reading data to Google Powermeter variabile using btach update.";
        const string threadname = "GBUploader";
        const string feedUrl = "https://www.google.com/powermeter/feeds/event";
        const string webHeader = "Authorization";
        const string cvariablepath = "https://www.google.com/powermeter/feeds/user/{0}/{0}/variable/{1}.{2}";

        // plugin own varibles.
        bool RunUpdate = false;
        NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
        Thread tDriverThread;
        DateTime startTime, currentTime, prevTime;
        float KWh;
        Queue ReadingsQueue = new Queue();


        // plugin setting from app.config file (values fix during init method)
        string authString;
        string variablePath;
        int update_cycle;
        bool usepcclock;
        
        #region IEnergyMonitorPlugin Members

        string IEnergyMonitorPlugin.Name
        {
            get { return pluginname; }
        }

        string IEnergyMonitorPlugin.Description
        {
            get { return plugindescription; }
        }

        bool IEnergyMonitorPlugin.Enabled
        {
            get { return bool.Parse(GetSetting("googleplugin_enabled", "false")); }
        }

        bool IEnergyMonitorPlugin.Init()
        {
            logger.Trace("Starting Init.");

            authString = GetSetting("googleplugin_authString", "your_auth_String");
            update_cycle = Convert.ToInt16(GetSetting("googleplugin_update_interval", "600"));
            usepcclock = Convert.ToBoolean(GetSetting("googleplugin_usepcclock", "true"));

            string userId = GetSetting("googleplugin_userId", "your_userId");
            string deviceId = GetSetting("googleplugin_deviceId", "your_auth_deviceId");
            string variable = GetSetting("googleplugin_variable", "your_auth_variable");
            variablePath = string.Format(cvariablepath, userId, deviceId, variable);

            logger.Debug("googleplugin_userId {0}", userId);
            logger.Debug("googleplugin_deviceId {0}", deviceId);
            logger.Debug("googleplugin_variable {0}", variable);
            logger.Debug("variablePath {0}", variablePath);
            logger.Debug("googleplugin_authString {0}", authString);
            logger.Debug("update_cycle {0}", update_cycle);

            // Start thread for update activities.
            tDriverThread = new Thread(new ThreadStart(GoogleBatchUploader));
            tDriverThread.Name = pluginname;
            tDriverThread.Start();
            
            return true;
        }

        bool IEnergyMonitorPlugin.Notify(EnergyReading rdg)
        {
            DateTime refTimeStamp;
            logger.Trace("Notify start - {0}",  rdg.TimeStamp.ToLongTimeString() + " - " + rdg.Energy.ToString("N") + " Watts - " + rdg.Temperature.ToString("N") + " Degrees" );
            if (usepcclock)
            {
                refTimeStamp = DateTime.Now;
            }
                else
            {
                refTimeStamp = rdg.TimeStamp;
            }
            if (startTime == default(DateTime))
            {
                // First notify execution: set starting values.
                startTime = refTimeStamp;
                currentTime = refTimeStamp;
                KWh = 0;
                logger.Trace("Set startTime to - {0}", startTime.ToLongTimeString());
            }
            // New value from envi: set interval references and add energy after kWh conversion.
            prevTime = currentTime;
            currentTime = refTimeStamp;
            if( currentTime != prevTime)
            {
                KWh += (rdg.Energy / ((60 / (float)currentTime.Subtract(prevTime).TotalSeconds) * 60)) / 1000;
            }
            logger.Trace("Notify end startTime {0} - currentTime {1} - Watts {2}", startTime.ToLongTimeString(), currentTime.ToLongTimeString(), KWh);
            return true;
        }

        bool IEnergyMonitorPlugin.DeInit()
        {
            logger.Trace("DeInit.");
            if (RunUpdate == true)
            {
                logger.Trace("DeInit - aborting thread.");
                RunUpdate = false;
                tDriverThread.Abort();
            }
        return true;
        }

        NameValueCollection IEnergyMonitorPlugin.Configure(NameValueCollection current)
        {
            NameValueCollection retVal = null;

            frmConfig frm = new frmConfig(current);
            DialogResult dlg = frm.ShowDialog();

            if (DialogResult.OK == dlg)
            {
                retVal = new NameValueCollection();
                retVal.Add("googleplugin_enabled", frm.Enabled.ToString().ToLower());
                retVal.Add("googleplugin_update_interval", frm.Interval.ToString());
                retVal.Add("googleplugin_usepcclock", frm.PCClock.ToString());
                retVal.Add("googleplugin_authstring", frm.AuthString);
                retVal.Add("googleplugin_userid", frm.UserId);
                retVal.Add("googleplugin_deviceid", frm.DeviceId);
                retVal.Add("googleplugin_variable", frm.Variable);
            }
            return retVal;
        }

        #endregion

        private string GetSetting(string key, string defaultValue)
        {
            NameValueCollection appSettings = ConfigurationManager.AppSettings;
            string val = defaultValue;
            try
            {
                string v = appSettings.Get(key);
                if (null != v)
                {
                    val = v;
                }
            }
            catch
            {
                // just ignore
            }
            return val;
        }


        private void GoogleBatchUploader()
        {
            int i;
            string xmlEntry;
            string response;
            EnergyData eg;
            int ReadingsToUpload;

            RunUpdate = true;

            while( RunUpdate )
            {
                logger.Trace("Thread {0} - Start update cycle.", threadname);
                logger.Trace("Data to upload: startTime {0} - currentTime {1} - Watts {2}", startTime.ToLongTimeString(), currentTime.ToLongTimeString(), KWh);

                // Add cycle data to send queue.
                eg.startTime = startTime;
                eg.duration = (int)currentTime.Subtract(startTime).TotalSeconds;
                eg.KWh = KWh;
                if (eg.duration != 0)
                {
                    ReadingsQueue.Enqueue(eg);
                    KWh = 0;
                    startTime = currentTime;
                    // currentTime = currentTime;
                    logger.Trace("Enqueued: startTime {0} - currentTime {1} - Watts {2}", eg.startTime.ToLongTimeString(), eg.duration, eg.KWh);
                }

                // Send all data in queue.
                ReadingsToUpload = ReadingsQueue.Count;
                // Set max element to 1000 due to google limit (max 1000 values for api call).
                if (ReadingsToUpload > 1000)
                {
                    ReadingsToUpload = 1000;
                }

                xmlEntry = @"
                    <feed xmlns=""http://www.w3.org/2005/Atom""
                    xmlns:meter=""http://schemas.google.com/meter/2008""
                    xmlns:batch=""http://schemas.google.com/gdata/batch"">";

                // Create a <entry> element for each value in queue.
                for (i = 1; i <= ReadingsToUpload; i++)
                {
                    eg = (EnergyData)ReadingsQueue.Peek();
                    logger.Trace("Dequeued: startTime {0} - currentTime {1} - Watts {2}", eg.startTime.ToLongTimeString(), eg.duration, eg.KWh);

                    xmlEntry += string.Format(@"
                    <entry>
                    <category scheme=""http://schemas.google.com/g/2005#kind"" term=""http://schemas.google.com/meter/2008#durMeasurement""/>
                    <meter:subject>
                    {0}
                    </meter:subject>
                    <batch:id>A.1</batch:id>
                    <meter:startTime uncertainty=""1.0"">{1}</meter:startTime>
                    <meter:duration uncertainty=""1.0"">{2}</meter:duration>
                    <meter:quantity uncertainty=""0.001"" unit=""kW h"">{3}</meter:quantity>
                    </entry>", variablePath, XmlConvert.ToString(eg.startTime),eg.duration, XmlConvert.ToString(eg.KWh));
                }
                xmlEntry += string.Format(@"</feed>");
                
                logger.Debug("Batch data pack: {0}", xmlEntry);

                try
                {
                    // Prepare to send data to google.
                    WebClient wc = new WebClient();
                    WebHeaderCollection whc = new WebHeaderCollection();

                    //set the required content type
                    whc.Add("Content-Type", "application/atom+xml");

                    //set the token to the authorization header
                    string authSubHeader = string.Format("AuthSub token=\"{0}\"", authString);
                    whc.Add(webHeader, authSubHeader);
                    wc.Headers = whc;

                    logger.Trace("Try to send data to Google.");
                    response = wc.UploadString(feedUrl, "POST", xmlEntry);

                    // Analyze google response for return codes.
                    logger.Debug("Google response: {0}", response);

                    XmlDocument XmlResponse = new XmlDocument();
                    XmlResponse.LoadXml(response);
                    XmlNodeList list = XmlResponse.GetElementsByTagName("batch:status");

                    logger.Trace("Check Google return codes.");

                    i = 0;
                    foreach (XmlElement rc in list)
                    {
                        if (rc.GetAttribute("code") == "201")
                        {
                            i++;
                        }
                    }
                    // Test if we have received an ok status code for each <entry> element sent.
                    bool responseOk = (i == ReadingsToUpload);
                    logger.Debug("Check Google return codes - ok: {0} - response: {1}", i, responseOk);

                    if (responseOk)
                    {
                        // Data successfully sent so remove them from queue.
                        for (i = 1; i <= ReadingsToUpload; i++)
                        {
                            ReadingsQueue.Dequeue();
                        }
                    }
                }

                catch (Exception ex)
                {
                    logger.Error("Exption sending data: {0}", ex.ToString());
                }

                //wait for next update cycle.
                logger.Debug("Wait for next update cycle.");
                Thread.Sleep(update_cycle * 1000);
            }
            RunUpdate = false;
        }    
    }
}
